package com.conversationboard.model.propertybag;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import com.conversationboard.database.Database;
import com.conversationboard.database.Queries;

/**
 * Class that wrappers reading and writing items to the database property bag.
 * The property bag is a set of key-value pairs that allow you to hold data
 * that's not best suited to holding in a relational form (the output from batch
 * jobs, for example) as XML data, typically.
 * 
 * Keeps everything in the property bag in memory. So be careful to not put a
 * vast amount of data into it.
 * 
 * Synchronised with read/write locking, since writing happens extremely
 * infrequently.
 * 
 * Also, prior to going to the database, it checks to see whether it's already
 * tried the database since the server has been started, to ensure no redundant
 * reads.
 * 
 * @author Keith Watson
 * 
 */

public class PropertyBag {

    private static Map<String, Property> properties = new HashMap<String, Property>();
    private static Map<String, String> alreadyTried = new HashMap<String, String>();
    private static ReadWriteLock lockManager = new ReentrantReadWriteLock();


    /**
     * Put a value into the database's property bag.
     * 
     * @param key the key
     * @param value the value
     */

    public static void put(String key, String value) throws SQLException {

        Lock lock = lockManager.writeLock();
        lock.lock();

        Connection connection = null;

        try {

            connection = Database.getConnection();

            PreparedStatement statement = connection.prepareStatement(Queries.UPDATE_PROPERTY);
            statement.setString(1, value);
            statement.setString(2, key);
            int updatedRowCount = statement.executeUpdate();
            statement.close();

            /*
             * If we tried to update, but didn't find a row, then insert the
             * property
             */

            if (updatedRowCount == 0) {
                statement = connection.prepareStatement(Queries.INSERT_INTO_PROPERTY_BAG);
                statement.setString(1, key);
                statement.setString(2, value);
                statement.executeUpdate();
                statement.close();
            }

            /* Now put it in the cache */

            Property property = new Property();
            property.setKey(key);
            property.setLastUpdated(new Date());
            property.setValue(value);

            properties.put(key, property);

        } finally {
            Database.close(connection);
            lock.unlock();
        }

    }


    public static Property get(String key) throws SQLException {

        Connection connection = null;
        String value = "";
        Timestamp lastUpdated = null;

        Lock lock = lockManager.readLock();
        lock.lock();

        try {

            Property property = properties.get(key);

            /* First check in the cache */

            if (property != null) {
                return property;
            } else {

                /*
                 * If I've already tried looking in the database and didn't find
                 * it, just return null without going to the database to save a
                 * query.
                 */

                if (alreadyTried.containsKey(key)) {
                    return null;
                }

                /*
                 * If I haven't already tried, then go to the database. If I get
                 * it, stick it in the cache; if I don't get it, stick it in the
                 * list of keys I've already tried so I don't bother trying
                 * again.
                 */

                connection = Database.getConnection();

                PreparedStatement statement = connection.prepareStatement(Queries.GET_FROM_PROPERTY_BAG);
                statement.setString(1, key);
                statement.execute();

                ResultSet resultSet = statement.getResultSet();

                boolean found = false;

                while (resultSet.next()) {
                    found = true;
                    value = resultSet.getString("propertyvalue");
                    lastUpdated = resultSet.getTimestamp("lastupdated");
                }

                Property databaseProperty = null;

                if (found) {
                    databaseProperty = new Property();
                    databaseProperty.setKey(key);
                    databaseProperty.setValue(value);
                    databaseProperty.setLastUpdated(lastUpdated);
                    properties.put(key, databaseProperty);
                } else {
                    databaseProperty = null;
                    alreadyTried.put(key, key);
                }

                return databaseProperty;
            }

        } finally {
            Database.close(connection);
            lock.unlock();
        }
    }

}
